//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using ManagementStudioServices;
using ManagementStudioServices.Interfaces;
using Microsoft.Research.ScientificWorkflow.ManagementStudioControls;
using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.Common;
using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.Interfaces;
using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.WorkflowManagerModule;
using Microsoft.Research.ScientificWorkflow.TridentComposer;
using Microsoft.Research.ScientificWorkflow.TridentUtilities;
using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
using Microsoft.Practices.Unity;
using SR = Microsoft.Research.DataLayer;
using TridentAPI;
using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.RegistryManagerModule;
using System.ComponentModel;
using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.HealthCheck;
using System.Threading;
using System.Configuration;
using Microsoft.Research.ScientificWorkflow.UIDesigner;
using Microsoft.Research.ScientificWorkflow.TridentModel;
using System.Globalization;
using Microsoft.Research.ScientificWorkflow.SecurityControls;
using System.Windows.Threading;
using Microsoft.Research.DataLayer;

namespace Microsoft.Research.ScientificWorkflow.ManagementStudio
{
    /// <summary>
    /// Interaction logic for Managementstudio.xaml
    /// </summary>
    public sealed partial class ManagementStudioWindow : Window, IRegionManager, IDisposable
    {
        /// <summary>
        /// The connection manager.
        /// </summary>
        private SR.ConnectionManager connMgr;

        /// <summary>
        /// The registry connection.
        /// </summary>
        private SR.Connection registryConnection;

        /// <summary>
        /// The Unity container instance used for dependency injection.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// The presentation model for the left pane.
        /// </summary>
        private LeftPanePresenter leftPaneController;

        /// <summary>
        /// The workflow mananger service.
        /// </summary>
        private IWorkflowManagerService worklfowManagerService;

        /// <summary>
        /// The registry manager service.
        /// </summary>
        private IRegistryManagerService registryManagerService;

        /// <summary>
        /// List of modules in the application.
        /// </summary>
        private List<IModule> moduleList = new List<IModule>();

        /// <summary>
        /// The health check thread.
        /// </summary>
        private Thread healthCheckThread;

        /// <summary>
        /// The health check logic.
        /// </summary>
        private HealthCheckService healthCheckService;

        /// <summary>
        /// The datatypes info dictionary. Contains the color codes and the names of the recognized datatypes.
        /// </summary>
        private Dictionary<String, DataTypeModel> dataTypes = new Dictionary<string, DataTypeModel>();

        /// <summary>
        /// The constructor for the window.
        /// </summary>
        public ManagementStudioWindow()
        {
            InitializeComponent();
            System.Windows.Forms.Application.EnableVisualStyles();
            System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);

            this.Dispatcher.UnhandledException += new DispatcherUnhandledExceptionEventHandler(this.OnUnhandledException);
            this.Loaded += new RoutedEventHandler(ManagementStudioWindow_Loaded);
            this.Closing += new System.ComponentModel.CancelEventHandler(this.OnClosing);
        }

        /// <summary>
        /// This is handled essentially for preventing app crashes and doing appropriate logging.
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">DispatcherUnhandledExceptionEventArgs</param>
        private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            TridentErrorHandler.HandleKnownExceptionsInUI(this, e.Exception);
            if (e.Exception != null && (e.Exception is BackendStorageException || e.Exception.InnerException is BackendStorageException))
            {
                e.Handled = true;
            }
        }

        /// <summary>
        /// The event handler for the loaded event of the window.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The event args.</param>
        void ManagementStudioWindow_Loaded(object sender, RoutedEventArgs e)
        {
            this.InitializeRegistry();
            this.InitializeConnection();
            this.ReadDatatypes();
            this.InitializeContainer();
            this.InitializeServices();
            this.InitializeHealthCheckThread();
            this.InitializeLeftPane();
            this.InitializeModules();
        }

        /// <summary>
        /// Authenticates the user.
        /// </summary>
        private void AuthenticateUser()
        {
            TridentAuthentication authendicate = new TridentAuthentication(this.registryConnection);
            authendicate.AuthenticateUser();

            // If the user is guest, then dont allow him to access the application
            if (TridentAuthentication.LoggedUserRole == UserRole.Guest)
            {
                btnSystemPolicies.Visibility = Visibility.Collapsed;

                TridentErrorHandler.ThrowTridentCustomException(
                TridentErrorConstants.ErrorNumber1000000072,
                TridentAuthentication.INVALIDLOGINERROR);
            }
            else if (TridentAuthentication.LoggedUserRole == UserRole.NormalUser)
            {
                btnSystemPolicies.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Initializes the registry.
        /// </summary>
        private void InitializeRegistry()
        {
            try
            {
                // Initialize Connection.
                SR.SR_Connection.Init();
                connMgr = SR.ConnectionManager.Create(SR.ConnectionManager.CancelBehavior.ThrowException);
                connMgr.UIStyle = SR.ConnectionManagerBase.UIStyleType.Lite;
            }
            catch (Microsoft.Research.DataLayer.ConnectionFailure connectionFailure)
            {
                TridentErrorHandler.HandleUIException(connectionFailure);
                TridentMessageBox.ShowTridentErrorMessageBox(connectionFailure.InnerException.Message);
                Environment.Exit(0);
            }
            catch (InvalidOperationException exception)
            {
                TridentErrorHandler.HandleUIException(exception);
                TridentMessageBox.ShowTridentErrorMessageBox(exception.Message);
                Environment.Exit(0);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// Initializes the unity container.
        /// </summary>
        private void InitializeContainer()
        {
            this.container = new UnityContainer();
            this.container.RegisterInstance<IUnityContainer>(this.container, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance<IRegionManager>("MainWindow", this, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance(typeof(SR.Connection), this.registryConnection, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance<IStatusMessageControl>(this.StatusControl, new ExternallyControlledLifetimeManager());
            this.container.RegisterInstance<Dictionary<string, DataTypeModel>>("Datatypes", this.dataTypes, new ExternallyControlledLifetimeManager());
        }

        /// <summary>
        /// Initialize the left pane.
        /// </summary>
        private void InitializeLeftPane()
        {
            this.leftPaneController = this.container.Resolve(typeof(LeftPanePresenter)) as LeftPanePresenter;
            this.Add(this.leftPaneController.View, "leftPaneHost");
            this.container.RegisterInstance<ILeftPaneNavigator>(this.leftPaneController, new ExternallyControlledLifetimeManager());
        }

        /// <summary>
        /// Initialize the modules of the application.
        /// </summary>
        private void InitializeModules()
        {
            // Initialize the workflow manager module.
            WorkflowMgrModule workflowManagerModule = new WorkflowMgrModule(this.container, this);
            workflowManagerModule.Initialize();
            this.moduleList.Add(workflowManagerModule);

            // Initialize the registry manager module.
            RegistryManager registryManagerModule = new RegistryManager(this.container, this);
            registryManagerModule.Initialize();
            this.moduleList.Add(registryManagerModule);

            // select the default module.
            this.leftPaneController.SelectDefault();
        }

        /// <summary>
        /// Initialize the registry connection.
        /// </summary>
        private void InitializeConnection()
        {
            bool isValid;

            SR.BackendStorageException connFailure;
            if (connMgr.HasDefaultConnection && !connMgr.TestConnection(connMgr.DefaultConnection, out connFailure))
            {
                TridentMessageBox.ShowTridentErrorMessageBox(connFailure.InnerException.Message);
                isValid = false;
            }
            else
            {
                isValid = true;
            }

            do
            {
                try
                {
                    if (!isValid)
                    {
                        registryConnection = connMgr.PickConnection(SR.ConnectionManager.ConnectionUI.AlwaysShowUI);
                    }
                    else
                    {
                        registryConnection = connMgr.PickConnection(SR.ConnectionManager.ConnectionUI.PromptIfNeeded);
                    }

                    if (null != registryConnection)
                    {
                        if (!Helper.IsValidConnection(registryConnection))
                        {
                            TridentMessageBox.ShowTridentErrorMessageBox(
                                TridentCommonResourceManager.GetString("InvalidRegistryConnection"));
                            isValid = false;
                            continue;
                        }

                        registryConnection.AutoSave = false;
                        this.AuthenticateUser();
                    }

                    isValid = true;
                }
                catch (SR.ConnectionFailure connectionFailure)
                {
                    TridentErrorHandler.HandleUIException(connectionFailure);
                    TridentMessageBox.ShowTridentErrorMessageBox(connectionFailure.InnerException.Message);
                    isValid = false;
                }
                catch (InvalidOperationException exception)
                {
                    TridentErrorHandler.HandleUIException(exception);
                    TridentMessageBox.ShowTridentErrorMessageBox(exception.Message);
                    isValid = false;
                }
                catch (TridentCustomException customExp)
                {
                    TridentErrorHandler.HandleTridentCustomExceptionInUI(customExp, false);
                    isValid = false;
                }
                catch (Exception exception)
                {
                    TridentErrorHandler.HandleKnownExceptionsInUI(exception, false);
                    isValid = false;
                }
            } while (!isValid);

            if (null == registryConnection)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(
                    TridentCommonResourceManager.GetString("RegistryNotSelected"));
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// Initialize the services. These services interact with the registry.
        /// </summary>
        private void InitializeServices()
        {
            TypeProviderHelper.Initialize(this.registryConnection);

            SR.ExternalRegistryHelperProxy.Initialize(this.registryConnection);

            // Initialize the workflow manager service.
            this.worklfowManagerService = new WorkflowManagerService(this.registryConnection);
            this.container.RegisterInstance<IWorkflowManagerService>(this.worklfowManagerService, new ExternallyControlledLifetimeManager());

            this.registryManagerService = new RegistryManagerService(this.registryConnection);
            this.container.RegisterInstance<IRegistryManagerService>(this.registryManagerService, new ExternallyControlledLifetimeManager());

            // CategoriesComposer.CreateUser(Environment.UserName, this.registryConnection);
        }

        /// <summary>
        /// 
        /// </summary>
        private void InitializeHealthCheckThread()
        {
            try
            {
                // Start the health check thread.
                this.healthCheckService = new HealthCheckService(this.registryConnection.Clone());
                healthCheckService.ClientRefreshTime = Int32.Parse(ConfigurationManager.AppSettings["ClientRefreshTimeInSeconds"], CultureInfo.CurrentCulture);
                healthCheckService.HeartbeatTimeout = Int32.Parse(ConfigurationManager.AppSettings["HeartbeatTimeoutInSeconds"], CultureInfo.CurrentCulture);

                this.healthCheckThread = new Thread(healthCheckService.PerformHealthCheck);
                this.healthCheckThread.Start();

                this.container.RegisterInstance<HealthCheckService>(healthCheckService, new ExternallyControlledLifetimeManager());
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }
        }

        /// <summary>
        /// Open Advance connection Manager Dialog.
        /// </summary>
        /// <param name="sender">
        /// Connection button.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnAdvancedConnectionClick(object sender, RoutedEventArgs e)
        {
            this.ShowConnectionManager(SR.ConnectionManagerBase.UIStyleType.Normal);
        }

        /// <summary>
        /// Open Lite connection Manager Dialogue.
        /// </summary>
        /// <param name="sender">
        /// Connection button.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnSavedConnectionClick(object sender, RoutedEventArgs e)
        {
            this.ShowConnectionManager(SR.ConnectionManagerBase.UIStyleType.Lite);
            
        }

        /// <summary>
        /// Shows the connection manager dialog.
        /// </summary>
        /// <param name="styleType">
        /// Dialog type.
        /// </param>
        private void ShowConnectionManager(ConnectionManagerBase.UIStyleType styleType)
        {
            this.connMgr.UIStyle = styleType;

            try
            {
                if (this.leftPaneController != null)
                {
                    this.leftPaneController.SaveChanges();
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }

            try
            {
                if (this.ReInitializeConnection())
                {
                    this.RefreshManagementStudio();
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }
        }

        /// <summary>
        /// ReInitialize connection.
        /// </summary>
        /// <returns>
        /// True if Connection successfully created, False otherwise.
        /// </returns>
        private bool ReInitializeConnection()
        {
            bool isValid;
            bool connectionChanged = false;
            do
            {
                try
                {
                    SR.Connection conn = connMgr.PickConnection(SR.ConnectionManager.ConnectionUI.AlwaysShowUI);
                    if (conn != null && conn.Alive)
                    {
                        if (!Helper.IsValidConnection(conn))
                        {
                            TridentMessageBox.ShowTridentErrorMessageBox(
                                TridentCommonResourceManager.GetString("InvalidRegistryConnection"));
                            isValid = false;
                            continue;
                        }

                        connectionChanged = true;
                        this.registryConnection = conn;

                        // Disable AutoSave.
                        registryConnection.AutoSave = false;

                        // Check whether user exist or not if not then create new user in registry.
                        CategoriesComposer.CreateUser(TridentAuthentication.LoggedUserInUserName, registryConnection);

                        // Authenticate user in the new connection.
                        this.AuthenticateUser();

                        // Resets the system policy.
                        SR.SystemPolicy.Reset();
                    }

                    isValid = true;
                }
                catch (SR.ConnectionFailure connectionFailure)
                {
                    TridentErrorHandler.HandleUIException(connectionFailure);
                    TridentMessageBox.ShowTridentErrorMessageBox(connectionFailure.InnerException.Message);
                    isValid = false;
                }
                catch (TridentCustomException customExp)
                {
                    TridentErrorHandler.HandleTridentCustomExceptionInUI(customExp, false);
                    isValid = false;
                }
                catch (Exception ex)
                {
                    TridentErrorHandler.HandleKnownExceptionsInUI(ex, false);
                    isValid = false;
                }
            } while (!isValid);

            return connectionChanged;
        }

        /// <summary>
        /// Open System Policy Dialogue.
        /// </summary>
        /// <param name="sender">
        /// System Policy button.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnSystemPolicyClick(object sender, RoutedEventArgs e)
        {
            try
            {
                SR.SystemPolicy policy = this.registryManagerService.GetSystemPolicy();
                SecurityPolicyPresenter presenter = new SecurityPolicyPresenter(policy, this.registryManagerService);
            }
            catch (TridentCustomException ex)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(ex);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }
        }

        /// <summary>
        /// Called when [global settings click].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnGlobalSettingsClick(object sender, RoutedEventArgs e)
        {
            GlobalSettingsPresenter presenter = new GlobalSettingsPresenter(this.registryManagerService);
        }

        /// <summary>
        /// Refresh the management studio. A refresh is needed if the user changes the connection.
        /// </summary>
        private void RefreshManagementStudio()
        {
            this.container.Dispose();
            this.ClearRegionManager();
            this.moduleList.Clear();
            this.AbortHealthCheckThread();

            this.InitializeContainer();
            this.InitializeServices();
            this.InitializeHealthCheckThread();
            this.InitializeLeftPane();
            this.InitializeModules();
        }

        /// <summary>
        /// Clear the region manager of the UI elements added by modules.
        /// </summary>
        private void ClearRegionManager()
        {
            this.ClearRegion("leftPaneHost");
            this.ClearRegion("workflowManagerTablehost");
        }

        /// <summary>
        /// Clear the required region.
        /// </summary>
        /// <param name="regionName">The region to be cleared.</param>
        private void ClearRegion(string regionName)
        {
            Panel region = this.FindName(regionName) as Panel;
            if (region != null)
            {
                region.Children.Clear();
            }
        }

        #region IRegionManager Members

        /// <summary>
        /// Add the UI element into the region specified.
        /// </summary>
        /// <param name="view">The UI element to be added.</param>
        /// <param name="regionName">The region name.</param>
        public void Add(object view, string regionName)
        {
            Panel region = this.FindName(regionName) as Panel;
            if (region != null)
            {
                region.Children.Add(view as UIElement);
            }
        }

        /// <summary>
        /// Sets the title.
        /// </summary>
        /// <param name="title">The title.</param>
        public void SetTitle(string title)
        {
            if (!string.IsNullOrEmpty(title) && !string.IsNullOrEmpty(title.Trim()))
            {
                this.Title = title.Trim();
            }
            else
            {
                this.Title = ManagementStudioResourceManager.GetString("ManagementStudioHeader");
            }
        }
        #endregion

        #region IDisposable Members

        /// <summary>
        /// Operation to perform while Disposing the object.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// nullify and Disposes the object 
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                SR.ExternalRegistryHelperProxy.CleanUp();

                this.container.Dispose();
            }
        }
        #endregion

        /// <summary>
        /// Handles the Click event of the AboutButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void AboutButton_Click(object sender, RoutedEventArgs e)
        {
            AboutBox aboutBox = new AboutBox();
            aboutBox.Owner = Application.Current.MainWindow;
            aboutBox.ShowDialog();
        }

        /// <summary>
        /// Called when closing of the application has started.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void OnClosing(object sender, CancelEventArgs e)
        {
            SaveStateHandler saveState = this.leftPaneController.SaveChanges();
            if (saveState != null && saveState.SaveOptionSelected == SaveStatus.Invalid)
            {
                e.Cancel = true;
                return;
            }

            try
            {
                this.AbortHealthCheckThread();

                // serialize the datatype xml.
                UIHelper.SaveUserDataTypeXml(this.dataTypes);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }
        }

        /// <summary>
        /// Abort the health check thread.
        /// </summary>
        private void AbortHealthCheckThread()
        {
            if (healthCheckThread != null)
            {
                this.healthCheckThread.Abort();
            }
        }

        /// <summary>
        /// Read the datatypes.
        /// </summary>
        private void ReadDatatypes()
        {
            DataTypesComposer.ReadAllDataTypes(this.dataTypes);
            UIHelper.ReadUserDataTypeXml(this.dataTypes);
        }
    }
}
